#!/usr/bin/env php
<?php

declare(strict_types=1);

/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2017 Tyson Andre
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * @phan-file-suppress PhanPluginRemoveDebugEcho
 */

// @phan-file-suppress PhanMissingRequireFile this depends on where Phan is installed
if (file_exists(__DIR__ . "/../../../../vendor/autoload.php")) {
    require __DIR__ . "/../../../../vendor/autoload.php";
} else {
    require __DIR__ . "/../vendor/autoload.php";
}
use Microsoft\PhpParser\Parser;
use Phan\AST\TolerantASTConverter\TolerantASTConverter;

dump_main();

/**
 * Dumps a snippet provided as a command line argument
 * @throws Exception if it can't render the AST
 */
function dump_main(): void
{
    /** @return never */
    $print_help = static function (int $exit_code): void {
        global $argv;
        $help = <<<"EOB"
Usage: php [--help|-h|help] [--php-ast|--php-ast-native|--php-ast-with-placeholders|--tokens] {$argv[0]} 'snippet'
E.g.
  {$argv[0]} '2+2;'
  {$argv[0]} '<?php function test() {}'
  {$argv[0]} "$(cat 'path/to/file.php')"

  --php-ast:
    Pretty print the ast\Node generated by the polyfill parser.

  --php-ast-native:
    Pretty print the ast\Node generated by the native php-ast parser.

  --php-ast-with-placeholders:
    Pretty print the ast\Node generated by the polyfill parser when placeholders are requested (used by code completion in the language server).

  --tokens:
    Print a readable representation of the tokens parsed by token_get_all().

  --help, -h:
    Print this help message

EOB;
        echo $help;
        exit($exit_code);
    };
    error_reporting(E_ALL);
    global $argv;

    $as_php_ast = false;
    $as_tokens = false;
    $as_php_ast_with_placeholders = false;
    $as_php_ast_native = false;
    foreach ($argv as $i => $arg) {
        if ($arg === '--php-ast') {
            $as_php_ast = true;
        } elseif ($arg === '--php-ast-native') {
            $as_php_ast = true;
            $as_php_ast_native = true;
        } elseif ($arg === '--php-ast-with-placeholders') {
            $as_php_ast = true;
            $as_php_ast_with_placeholders = true;
        } elseif ($arg === '--tokens') {
            $as_tokens = true;
        } elseif (in_array($argv[$i], ['help', '-h', '--help'], true)) {
            $print_help(0);
        } else {
            continue;
        }
        unset($argv[$i]);
    }
    $argv = array_values($argv);

    if (count($argv) !== 2) {
        $print_help(1);
    }
    $expr = $argv[1];
    if (!is_string($expr)) {
        throw new AssertionError("missing 2nd argument");
    }

    // Guess if this is a snippet or file contents
    $add_prefix = ($expr[0] ?? '') !== '<' && \substr($expr, 0, 2) !== '#!';
    if ($add_prefix) {
        $expr = '<' . '?php ' . $expr;
    }

    if ($as_tokens) {
        $tokens = token_get_all($expr);
        if ($add_prefix) {
            unset($tokens[0]);
        }
        dump_tokens($tokens);
    } elseif ($as_php_ast) {
        dump_expr_as_ast($expr, $as_php_ast_with_placeholders, $as_php_ast_native);
    } else {
        dump_expr($expr);
    }
}

/**
 * Dump the list of tokens to the console
 * @param array<int, string|array{0:int, 1:string, 2:int}> $tokens
 */
function dump_tokens(array $tokens): void
{
    foreach ($tokens as $token) {
        if (is_string($token)) {
            echo $token . PHP_EOL;
            continue;
        }
        $kind = $token[0];
        if ($kind === T_WHITESPACE) {
            echo token_name($kind) . ': ' . var_export($token[1], true) . PHP_EOL;
            continue;
        }
        echo token_name($kind) . ': ' . $token[1] . PHP_EOL;
    }
}

/**
 * Parses $expr and echoes the compact AST representation to stdout.
 */
function dump_expr_as_ast(string $expr, bool $with_placeholders, bool $native): void
{
    if ($native) {
        $ast_data = ast\parse_code($expr, \Phan\Config::AST_VERSION);
    } else {
        $converter = new TolerantASTConverter();
        $converter->setShouldAddPlaceholders($with_placeholders);
        $ast_data = $converter->parseCodeAsPHPAST($expr, \Phan\Config::AST_VERSION);
    }
    echo \Phan\Debug::nodeToString($ast_data);
}

/**
 * Parses $expr and echoes the tolerant-php-parser AST to stdout.
 * @throws Exception
 */
function dump_expr(string $expr): void
{
    // Instantiate new parser instance
    $parser = new Parser();
    // Return and print an AST from string contents
    $ast_node = $parser->parseSourceFile($expr);
    TolerantASTConverter::unlinkDescendantNodes($ast_node);
    unset($ast_node->statementList[0]);
    $dumper = new \Phan\AST\TolerantASTConverter\NodeDumper($expr);
    $dumper->setIncludeTokenKind(true);
    $dumper->dumpTree($ast_node);
    echo "\n";
    // var_export($ast_node->statementList);
}
